#include "sdk_config.h"
#include "bcomdef.h"
#include "OSAL.h"
#include "linkdb.h"
#include "att.h"
#include "gatt.h"
#include "gatt_uuid.h"
#include "gatt_profile_uuid.h"
#include "peripheral.h"
#include "gattservapp.h"

//#include "ota.h"
#include "ota_service.h"
#include "ota_protocol.h"
#include "ota_flash.h"
#include "clock.h"
//#include "crc16.h"
#include "log.h"

#ifndef PROGRAM_TYPE
#define PROGRAM_TYPE 1
#endif

#ifndef OTA_BANK_MODE
#define OTA_BANK_MODE 0
#endif

#define MAX_DFU_PKT_LEN         20                                              /**< Maximum length (in bytes) of the DFU Packet characteristic. */
#define PKT_START_DFU_PARAM_LEN 2                                               /**< Length (in bytes) of the parameters for Packet Start DFU Request. */
#define PKT_INIT_DFU_PARAM_LEN  2                                               /**< Length (in bytes) of the parameters for Packet Init DFU Request. */
#define PKT_RCPT_NOTIF_REQ_LEN  3                                               /**< Length (in bytes) of the Packet Receipt Notification Request. */
#define MAX_PKTS_RCPT_NOTIF_LEN 6                                               /**< Maximum length (in bytes) of the Packets Receipt Notification. */
#define MAX_RESPONSE_LEN        7                                               /**< Maximum length (in bytes) of the response to a Control Point command. */
		

#define REG_RTC_CC1                (*((volatile unsigned int *)(0x4000f030)))
	
/**program type define .
 */
#define   MINI_BOOTLOADER_TYPE  0                       /**< mini bootloader type,bootloader without ble ability.*/
#define   OTA_BOOTLOADER_TYPE   1                       /**< ota bootloader type ,bootloader with ble ability.*/
#define   OTA_APPLICATION_TYPE  2                       /**< ota application type ,application with ble ability.*/

/**ota upgrade mode define .
 */
#define   DUAL_BANK_MODE 			 0                        /**<dual bank upgrade.*/
#define   SINGLE_BANK_MODE 		 1                        /**< single bank upgrade .*/

OTA_INFO_t otaInfo;
static void otaProtocol_ProcessEvt( OtaEvt_t *pEvt);
static otaProfileCBs_t otaProtocol_otaProfileCBs = {
	otaProtocol_ProcessEvt
};

static void otaProtocol_SendResponse(uint8_t rspOpcode, uint8_t rspValue)
{
	attHandleValueNoti_t notify;
	notify.value[0] = OTA_CODE_RESPONSE;
	notify.value[1] = rspOpcode;
	notify.value[2] = rspValue;
	notify.len = 3;
	OTAProfile_Notify(&notify);
}

static void otaProtocol_SendReceiptNotification(uint32_t numOfBytes)
{
	attHandleValueNoti_t notify;
	notify.value[0] = OTA_CODE_PKT_RCPT_NOTIF;
	notify.value[1] = BREAK_UINT32(numOfBytes, 0);
	notify.value[2] = BREAK_UINT32(numOfBytes, 1);
	notify.value[3] = BREAK_UINT32(numOfBytes, 2);
	notify.value[4] = BREAK_UINT32(numOfBytes, 3);
	notify.len = 5;
	OTAProfile_Notify(&notify);
}

static void otaProtocol_SendRecSizeResponse(uint32_t numOfBytes)
{
	attHandleValueNoti_t notify;
	notify.value[0] = OTA_CODE_RESPONSE;
	notify.value[1] = OTA_CODE_IMAGE_SIZE_REQ;
	notify.value[2] = OTA_RESP_VAL_SUCCESS;
	notify.value[3] = BREAK_UINT32(numOfBytes, 0);
	notify.value[4] = BREAK_UINT32(numOfBytes, 1);
	notify.value[5] = BREAK_UINT32(numOfBytes, 2);
	notify.value[6] = BREAK_UINT32(numOfBytes, 3);
	notify.len = 7;
	OTAProfile_Notify(&notify);
}

static void otaProtocol_ProcessControlEvt(uint8_t *dat, uint8_t len)
{
	uint8_t opcode;
	opcode = dat[0];
	otaInfo.ctlPointCmd = opcode;
	switch(opcode) {
		case OTA_CODE_START_OTA:
			#if (PROGRAM_TYPE == MINI_BOOTLOADER_TYPE)	//if ota process in bootloader, reboot system to start ota function.
				#if (OTA_BANK_MODE == DUAL_BANK_MODE)
					otaInfo.packetType = PKT_TYPE_START;
					otaInfo.imageType = dat[1];
					otaFlash_Init();
					LOG("mini OTA,ImageType=%d\r\n",otaInfo.imageType);
				#else
					#error "mini bootloader must in double bank mode!"
				#endif
			#elif (PROGRAM_TYPE == OTA_BOOTLOADER_TYPE)
					otaInfo.packetType = PKT_TYPE_START;
					otaInfo.imageType = dat[1];
					otaFlash_Init();
					LOG("bootloader OTA,ImageType=%d\r\n",otaInfo.imageType);
			#elif (PROGRAM_TYPE == OTA_APPLICATION_TYPE)
					#if (OTA_BANK_MODE == DUAL_BANK_MODE)
					LOG("reboot to dul ota.\r\n");
//				REG_RTC_CC1 = 0x01;
					write_reg(0x1FFF18FC,0x55AAAA55);
					GAPRole_TerminateConnection();
					WaitMs(500);
					NVIC_SystemReset();
				#elif (OTA_BANK_MODE == SINGLE_BANK_MODE)
					LOG("reboot to single ota.\r\n");
					enter_sigle_bank_ota();
					GAPRole_TerminateConnection();
					WaitMs(500);
					NVIC_SystemReset();
				#else
					#error "ota bootloader  bank err!"
				#endif
			#else
					#error "program type err!"
			#endif
			break;
		case OTA_CODE_RECEIVE_INIT:
			otaInfo.packetType = PKT_TYPE_INIT;
			if(dat[1] == 0x00) {		//Receive Init Packet
				otaInfo.initPacketLen = 0;
				osal_memset(otaInfo.initPacket,0,sizeof(otaInfo.initPacket));
			}
			else if(dat[1] == 0x01) {	//Init Packet Complete
				if(otaInfo.initPacketLen >= 2) {
					otaInfo.imageCRC16 = BUILD_UINT16(otaInfo.initPacket[otaInfo.initPacketLen-2],otaInfo.initPacket[otaInfo.initPacketLen-1]);
					otaProtocol_SendResponse(OTA_CODE_RECEIVE_INIT,OTA_RESP_VAL_SUCCESS);
					LOG("Image CRC=%04x\r\n",otaInfo.imageCRC16);
				}
				else {
					otaProtocol_SendResponse(OTA_CODE_RECEIVE_INIT,OTA_RESP_VAL_OPER_FAILED);
				}
			}
			else {	//Invalid State
				otaProtocol_SendResponse(OTA_CODE_RECEIVE_INIT,OTA_RESP_VAL_INVALID_STATE);
			}
			LOG("Receive Init\r\n");
			break;
		case OTA_CODE_RECEIVE_FW:
			otaInfo.packetType = PKT_TYPE_FIRMWARE_DATA;
			LOG("Receive Fw\r\n");
			break;
		case OTA_CODE_VALIDATE:
			otaInfo.packetType = PKT_TYPE_INVALID;
			if(otaFlash_CalculateCRC16(0, otaInfo.imageCRC16) == ERR_NONE) {
				otaProtocol_SendResponse(OTA_CODE_VALIDATE,OTA_RESP_VAL_SUCCESS);
				setBootloaderUpdateInfo();
				LOG("Validate OK\r\n");
			}
			else {
				otaProtocol_SendResponse(OTA_CODE_VALIDATE,OTA_RESP_VAL_CRC_ERROR);
				LOG("Validate Fail\r\n");
			}
			break;
		case OTA_CODE_ACTIVATE_N_RESET:
			LOG("Activate and reset\r\n");
			otaInfo.packetType = PKT_TYPE_INVALID;
			// Terminate Connection
			GAPRole_TerminateConnection();
			//otaInfo.imageType = 0;
			otaInfo.numOfNotifyPackets = 0xFFFF;
			otaInfo.initPacketLen = 0;
			otaInfo.blImageRecSize = 0;
			otaInfo.appImageRecSize = 0;
			otaInfo.numOfNotifyRecPackets = 0;
			WaitMs(500);
			NVIC_SystemReset();
			break;
		case OTA_CODE_SYS_RESET:
			LOG("Sys reset\r\n");
			otaInfo.packetType = PKT_TYPE_INVALID;
			// Terminate Connection
			GAPRole_TerminateConnection();
			//otaInfo.imageType = 0;
			otaInfo.numOfNotifyPackets = 0xFFFF;
			otaInfo.initPacketLen = 0;
			otaInfo.blImageRecSize = 0;
			otaInfo.appImageRecSize = 0;
			otaInfo.numOfNotifyRecPackets = 0;
			WaitMs(500);
			NVIC_SystemReset();
			break;
		case OTA_CODE_IMAGE_SIZE_REQ:
			otaInfo.packetType = PKT_TYPE_INVALID;
			if(otaInfo.imageType == OTA_IMAGE_TYPE_BOOTLOADER) {
				
				otaProtocol_SendRecSizeResponse(otaInfo.blImageRecSize);
			}
			else if(otaInfo.imageType == OTA_IMAGE_TYPE_APPLICATION) {
				otaProtocol_SendRecSizeResponse(otaInfo.appImageRecSize);
			}
			else {
				otaProtocol_SendResponse(OTA_CODE_RECEIVE_INIT,OTA_RESP_VAL_INVALID_STATE);
			}
			LOG("Image size Req\r\n");
			break;
		case OTA_CODE_PKT_RCPT_NOTIF_REQ:
			otaInfo.packetType = PKT_TYPE_INVALID;
			otaInfo.numOfNotifyPackets = BUILD_UINT16(dat[1], dat[2]);
			otaInfo.numOfNotifyRecPackets = 0;
			LOG("OTA RCPT number:%d\r\n", otaInfo.numOfNotifyPackets);
			break;
		default:
			break;
	}
}


static void otaProtocol_ProcessDataEvt(uint8_t *dat, uint8_t len)
{
	switch(otaInfo.packetType) {
		case PKT_TYPE_START:
			if(len == 12) {
				otaInfo.blImageSize = BUILD_UINT32(dat[4],dat[5],dat[6],dat[7]);
				otaInfo.appImageSize = BUILD_UINT32(dat[8],dat[9],dat[10],dat[11]);
				//otaInfo.imageType = 0;
				otaInfo.numOfNotifyPackets = 0xFFFF;
				otaInfo.initPacketLen = 0;
				otaInfo.blImageRecSize = 0;
				otaInfo.appImageRecSize = 0;
				otaInfo.numOfNotifyRecPackets = 0;
				otaProtocol_SendResponse(OTA_CODE_START_OTA,OTA_RESP_VAL_SUCCESS);
				LOG("BlSize=%d,AppSize=%d\r\n", otaInfo.blImageSize, otaInfo.appImageSize);
				erase_code_flash();
			}
			else {	//data length is error
				otaProtocol_SendResponse(OTA_CODE_START_OTA,OTA_RESP_VAL_NOT_SUPPORTED);
			}
			break;
		case PKT_TYPE_INIT:
			if(otaInfo.initPacketLen + len <= sizeof(otaInfo.initPacket)) {
				osal_memcpy(otaInfo.initPacket+otaInfo.initPacketLen, dat, len);
				otaInfo.initPacketLen += len;
			}
			else {
				otaProtocol_SendResponse(OTA_CODE_RECEIVE_INIT,OTA_RESP_VAL_NOT_SUPPORTED);
			}
			break;
		case PKT_TYPE_FIRMWARE_DATA:
			LOG("-");
			otaInfo.numOfNotifyRecPackets++;
			if(otaInfo.imageType == OTA_IMAGE_TYPE_BOOTLOADER) {
//				otaFlash_WriteData(dat, len);
				otaFlash_WriteData(dat, len,otaInfo.blImageSize);
				otaInfo.blImageRecSize += len;
				if(otaInfo.numOfNotifyRecPackets >= otaInfo.numOfNotifyPackets) {
					otaInfo.numOfNotifyRecPackets = 0;
					otaProtocol_SendReceiptNotification(otaInfo.blImageRecSize);
				}
				if(otaInfo.blImageRecSize >= otaInfo.blImageSize) {
					otaProtocol_SendResponse(OTA_CODE_RECEIVE_FW,OTA_RESP_VAL_SUCCESS);
				}
				
			}
			else if(otaInfo.imageType == OTA_IMAGE_TYPE_APPLICATION) {
//				otaFlash_WriteData(dat, len);
				otaFlash_WriteData(dat, len,otaInfo.appImageSize);
				otaInfo.appImageRecSize += len;
				if(otaInfo.numOfNotifyRecPackets >= otaInfo.numOfNotifyPackets) {
					otaInfo.numOfNotifyRecPackets = 0;
					otaProtocol_SendReceiptNotification(otaInfo.appImageRecSize);
				}
				if(otaInfo.appImageRecSize >= otaInfo.appImageSize) {
					otaProtocol_SendResponse(OTA_CODE_RECEIVE_FW,OTA_RESP_VAL_SUCCESS);
				}
			}
			break;
		default:
			break;
	}
}

static void otaProtocol_ProcessEvt( OtaEvt_t *pEvt)
{
	switch(pEvt->type) {
		case OTA_EVT_CONTROL:
			otaProtocol_ProcessControlEvt(pEvt->data, pEvt->len);
			break;
		case OTA_EVT_DATA:
			otaProtocol_ProcessDataEvt(pEvt->data, pEvt->len);
			break;
		case OTA_EVT_CONNECTED:
			
			break;
		case OTA_EVT_DISCONNECTED:
			
			break;
		case OTA_EVT_NOTIF_ENABLE:
			break;
		case OTA_EVT_NOTIF_DISABLE:
			break;
	}
}

int otaProtocol_init(void)
{
	OTAProfile_AddService(OTAPROFILE_SERVICE);
	OTAProfile_RegisterAppCBs(&otaProtocol_otaProfileCBs);
	otaFlash_Init();
	return ERR_NONE;
}

